summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2023-08-08 14:23:25 +0200
committerBug Magnet <marco.nikic@mullvad.net>2023-08-08 14:23:25 +0200
commit1ac353b00318bc8ec8787c08120144255adb77de (patch)
tree2717d36ef0af30b2e7644e89933c917072f0ff0d
parent652d163d575bab039a57c156b89628de66e472bd (diff)
parent6b3825130405e748db3313f082eee6266996b557 (diff)
downloadmullvadvpn-1ac353b00318bc8ec8787c08120144255adb77de.tar.xz
mullvadvpn-1ac353b00318bc8ec8787c08120144255adb77de.zip
Merge branch 'introduce-a-migration-manager-ios-242'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj14
-rw-r--r--ios/MullvadVPN/AppDelegate.swift37
-rw-r--r--ios/MullvadVPN/MigrationManager/MigrationManager.swift98
-rw-r--r--ios/MullvadVPN/SettingsManager/SettingsManager.swift66
-rw-r--r--ios/MullvadVPN/SettingsManager/SettingsStore.swift7
-rw-r--r--ios/MullvadVPN/SettingsManager/TunnelSettings.swift12
-rw-r--r--ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift12
7 files changed, 153 insertions, 93 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index cfddd66410..464240306d 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -421,6 +421,8 @@
A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A8A8EA2A262AB30086D569 /* FileCache.swift */; };
A9AD31D72A6AB68B00141BE8 /* InputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */; };
A9B2CF722A1F64CD0013CC6C /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
+ A9D96B1A2A8247C100A5C673 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D96B192A8247C100A5C673 /* MigrationManager.swift */; };
+ A9D96B1B2A8248F200A5C673 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D96B192A8247C100A5C673 /* MigrationManager.swift */; };
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */; };
A9D99BA02A1F7F3A00DE27D3 /* TransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */; };
A9D99BA52A1F808900DE27D3 /* RelayCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 063F02732902B63F001FA09F /* RelayCache.framework */; };
@@ -1210,6 +1212,7 @@
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; };
A9A8A8EA2A262AB30086D569 /* FileCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCache.swift; sourceTree = "<group>"; };
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCacheTests.swift; sourceTree = "<group>"; };
+ A9D96B192A8247C100A5C673 /* MigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = "<group>"; };
A9D99B9F2A1F7F3A00DE27D3 /* TransportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportProvider.swift; sourceTree = "<group>"; };
A9EC20E52A5C488D0040D56E /* Haversine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Haversine.swift; sourceTree = "<group>"; };
A9EC20E72A5D3A8C0040D56E /* CoordinatesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatesTests.swift; sourceTree = "<group>"; };
@@ -2154,6 +2157,7 @@
58C774C929AB543C003A1A56 /* Containers */,
58CAF9F22983D32200BE19F7 /* Coordinators */,
583FE02329C1AC9F006E85F9 /* Extensions */,
+ A9D96B182A82479700A5C673 /* MigrationManager */,
58B26E1F2943516500D5980C /* Notifications */,
586A950B2901250A007BAF2B /* Operations */,
5864859729A0D012006C5743 /* Presentation controllers */,
@@ -2326,6 +2330,14 @@
path = MullvadTransport;
sourceTree = "<group>";
};
+ A9D96B182A82479700A5C673 /* MigrationManager */ = {
+ isa = PBXGroup;
+ children = (
+ A9D96B192A8247C100A5C673 /* MigrationManager.swift */,
+ );
+ path = MigrationManager;
+ sourceTree = "<group>";
+ };
F028A5472A336E1900C0CAA3 /* RedeemVoucher */ = {
isa = PBXGroup;
children = (
@@ -3272,6 +3284,7 @@
E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */,
582BB1B1229569620055B6EF /* UINavigationBar+Appearance.swift in Sources */,
58ACF6492655365700ACE4B7 /* PreferencesViewController.swift in Sources */,
+ A9D96B1A2A8247C100A5C673 /* MigrationManager.swift in Sources */,
7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */,
58C774BE29A7A249003A1A56 /* CustomNavigationController.swift in Sources */,
E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */,
@@ -3461,6 +3474,7 @@
5838318B27C40A3900000571 /* Pinger.swift in Sources */,
06410E05292D0FC000AFC18C /* SettingsParser.swift in Sources */,
A92ECC292A7802AB0052F1B1 /* StoredDeviceData.swift in Sources */,
+ A9D96B1B2A8248F200A5C673 /* MigrationManager.swift in Sources */,
58E0729D28814AAE008902F8 /* PacketTunnelConfiguration.swift in Sources */,
58E0729F28814ACC008902F8 /* WireGuardLogLevel+Logging.swift in Sources */,
580F8B8428197884002E0998 /* TunnelSettingsV2.swift in Sources */,
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index c8003c068e..f1da222e3d 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -41,6 +41,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private(set) var storePaymentManager: StorePaymentManager!
private var transportMonitor: TransportMonitor!
private var relayConstraintsObserver: TunnelBlockObserver!
+ private let migrationManager = MigrationManager()
// MARK: - Application lifecycle
@@ -368,31 +369,33 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
let migrateSettingsOperation = AsyncBlockOperation(dispatchQueue: .main) { [self] finish in
- SettingsManager.migrateStore(with: proxyFactory) { [self] migrationResult in
- switch migrationResult {
- case .success:
- // Tell the tunnel to re-read tunnel configuration after migration.
- logger.debug("Reconnect the tunnel after settings migration.")
- tunnelManager.reconnectTunnel(selectNewRelay: true)
- fallthrough
+ migrationManager
+ .migrateSettings(store: SettingsManager.store, proxyFactory: proxyFactory) { [self] migrationResult in
+ switch migrationResult {
+ case .success:
+ // Tell the tunnel to re-read tunnel configuration after migration.
+ logger.debug("Reconnect the tunnel after settings migration.")
+ tunnelManager.reconnectTunnel(selectNewRelay: true)
+ fallthrough
- case .nothing:
- finish(nil)
+ case .nothing:
+ finish(nil)
- case let .failure(error):
- let migrationUIHandler = application.connectedScenes.first { $0 is SettingsMigrationUIHandler }
- as? SettingsMigrationUIHandler
+ case let .failure(error):
+ let migrationUIHandler = application.connectedScenes.first { $0 is SettingsMigrationUIHandler }
+ as? SettingsMigrationUIHandler
- if let migrationUIHandler {
- migrationUIHandler.showMigrationError(error) {
+ if let migrationUIHandler {
+ migrationUIHandler.showMigrationError(error) {
+ finish(error)
+ }
+ } else {
finish(error)
}
- } else {
- finish(error)
}
}
- }
}
+
migrateSettingsOperation.addDependencies([wipeSettingsOperation, loadTunnelStoreOperation])
let initTunnelManagerOperation = AsyncBlockOperation(dispatchQueue: .main) { finish in
diff --git a/ios/MullvadVPN/MigrationManager/MigrationManager.swift b/ios/MullvadVPN/MigrationManager/MigrationManager.swift
new file mode 100644
index 0000000000..f2357bcb83
--- /dev/null
+++ b/ios/MullvadVPN/MigrationManager/MigrationManager.swift
@@ -0,0 +1,98 @@
+//
+// MigrationManager.swift
+// MullvadVPN
+//
+// Created by Marco Nikic on 2023-08-08.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadLogging
+import MullvadREST
+import MullvadTypes
+
+enum SettingsMigrationResult {
+ /// Nothing to migrate.
+ case nothing
+
+ /// Successfully performed migration.
+ case success
+
+ /// Failure when migrating store.
+ case failure(Error)
+}
+
+struct MigrationManager {
+ private let logger = Logger(label: "MigrationManager")
+
+ /// Migrate settings store if needed.
+ ///
+ /// The following types of error are expected to be returned by this method:
+ /// `SettingsMigrationError`, `UnsupportedSettingsVersionError`, `ReadSettingsVersionError`.
+ func migrateSettings(
+ store: SettingsStore,
+ proxyFactory: REST.ProxyFactory,
+ migrationCompleted: @escaping (SettingsMigrationResult) -> Void
+ ) {
+ let handleCompletion = { (result: SettingsMigrationResult) in
+ // Reset store upon failure to migrate settings.
+ if case .failure = result {
+ SettingsManager.resetStore()
+ }
+ migrationCompleted(result)
+ }
+
+ do {
+ try checkLatestSettingsVersion(in: store)
+ handleCompletion(.nothing)
+ } catch {
+ handleCompletion(.failure(error))
+ }
+ }
+
+ private func checkLatestSettingsVersion(in store: SettingsStore) throws {
+ let settingsVersion: Int
+ do {
+ let parser = SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
+ let settingsData = try store.read(key: SettingsKey.settings)
+ settingsVersion = try parser.parseVersion(data: settingsData)
+ } catch .itemNotFound as KeychainError {
+ return
+ } catch {
+ 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
+ }
+}
+
+/// A wrapper type for errors returned by concrete migrations.
+struct SettingsMigrationError: LocalizedError, WrappingError {
+ private let inner: Error
+ let sourceVersion, targetVersion: SchemaVersion
+
+ var underlyingError: Error? {
+ inner
+ }
+
+ var errorDescription: String? {
+ "Failed to migrate settings from \(sourceVersion) to \(targetVersion)."
+ }
+
+ init(sourceVersion: SchemaVersion, targetVersion: SchemaVersion, underlyingError: Error) {
+ self.sourceVersion = sourceVersion
+ self.targetVersion = targetVersion
+ inner = underlyingError
+ }
+}
diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift
index 5ee563c475..91a73b8b07 100644
--- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift
+++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift
@@ -15,21 +15,10 @@ 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")
- private static let store: SettingsStore = KeychainSettingsStore(
+ static let store: SettingsStore = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)
@@ -134,32 +123,6 @@ enum SettingsManager {
try store.write(data, for: .deviceState)
}
- // MARK: - Migration
-
- /// Migrate settings store if 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 (SettingsMigrationResult) -> Void
- ) {
- let handleCompletion = { (result: SettingsMigrationResult) in
- // Reset store upon failure to migrate settings.
- if case .failure = result {
- resetStore()
- }
- completion(result)
- }
-
- do {
- try checkLatestSettingsVersion()
- handleCompletion(.nothing)
- } catch {
- handleCompletion(.failure(error))
- }
- }
-
/// Removes all legacy settings, device state and tunnel settings but keeps the last used
/// account number stored.
static func resetStore(completely: Bool = false) {
@@ -229,12 +192,7 @@ enum SettingsManager {
}
}
-enum SettingsKey: String, CaseIterable {
- case settings = "Settings"
- case deviceState = "DeviceState"
- case lastUsedAccount = "LastUsedAccount"
- case shouldWipeSettings = "ShouldWipeSettings"
-}
+// MARK: - Supporting types
/// An error type describing a failure to read or parse settings version.
struct ReadSettingsVersionError: LocalizedError, WrappingError {
@@ -266,26 +224,6 @@ struct UnsupportedSettingsVersionError: LocalizedError {
}
}
-/// A wrapper type for errors returned by concrete migrations.
-struct SettingsMigrationError: LocalizedError, WrappingError {
- private let inner: Error
- let sourceVersion, targetVersion: SchemaVersion
-
- var underlyingError: Error? {
- inner
- }
-
- var errorDescription: String? {
- "Failed to migrate settings from \(sourceVersion) to \(targetVersion)."
- }
-
- init(sourceVersion: SchemaVersion, targetVersion: SchemaVersion, underlyingError: Error) {
- self.sourceVersion = sourceVersion
- self.targetVersion = targetVersion
- inner = underlyingError
- }
-}
-
struct StringDecodingError: LocalizedError {
let data: Data
diff --git a/ios/MullvadVPN/SettingsManager/SettingsStore.swift b/ios/MullvadVPN/SettingsManager/SettingsStore.swift
index 03b587433d..55b565390f 100644
--- a/ios/MullvadVPN/SettingsManager/SettingsStore.swift
+++ b/ios/MullvadVPN/SettingsManager/SettingsStore.swift
@@ -8,6 +8,13 @@
import Foundation
+enum SettingsKey: String, CaseIterable {
+ case settings = "Settings"
+ case deviceState = "DeviceState"
+ case lastUsedAccount = "LastUsedAccount"
+ case shouldWipeSettings = "ShouldWipeSettings"
+}
+
protocol SettingsStore {
func read(key: SettingsKey) throws -> Data
func write(_ data: Data, for key: SettingsKey) throws
diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettings.swift b/ios/MullvadVPN/SettingsManager/TunnelSettings.swift
index a95eaf5802..91108d08be 100644
--- a/ios/MullvadVPN/SettingsManager/TunnelSettings.swift
+++ b/ios/MullvadVPN/SettingsManager/TunnelSettings.swift
@@ -10,3 +10,15 @@ import Foundation
/// Alias to the latest version of the `TunnelSettings`.
typealias LatestTunnelSettings = TunnelSettingsV2
+
+/// Settings and device state schema versions.
+enum SchemaVersion: Int, Equatable {
+ /// Legacy settings format, stored as `TunnelSettingsV1`.
+ case v1 = 1
+
+ /// New settings format, stored as `TunnelSettingsV2`.
+ case v2 = 2
+
+ /// Current schema version.
+ static let current = SchemaVersion.v2
+}
diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
index a530cfb03f..821a848878 100644
--- a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
+++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
@@ -13,18 +13,6 @@ import struct WireGuardKitTypes.IPAddressRange
import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey
-/// Settings and device state schema versions.
-enum SchemaVersion: Int, Equatable {
- /// Legacy settings format, stored as `TunnelSettingsV1`.
- case v1 = 1
-
- /// New settings format, stored as `TunnelSettingsV2`.
- case v2 = 2
-
- /// Current schema version.
- static let current = SchemaVersion.v2
-}
-
struct TunnelSettingsV2: Codable, Equatable {
/// Relay constraints.
var relayConstraints = RelayConstraints()