diff --git a/ios/MullvadSettings/TunnelSettingsUpdate.swift b/ios/MullvadSettings/TunnelSettingsUpdate.swift index 5b605fd5b1d5..ae7e319d7d27 100644 --- a/ios/MullvadSettings/TunnelSettingsUpdate.swift +++ b/ios/MullvadSettings/TunnelSettingsUpdate.swift @@ -9,6 +9,11 @@ import Foundation import MullvadTypes +// Note: +// Existing keys in `TunnelSettingsUpdate` must not be removed. +// They are required for backward compatibility. +// If a key is no longer used, mark it as deprecated instead of deleting it. +// Version upgrades should be handled in `upgradeToNextVersion()`. public enum TunnelSettingsUpdate: Sendable { case dnsSettings(DNSSettings) case obfuscation(WireGuardObfuscationSettings) diff --git a/ios/MullvadSettings/TunnelSettingsV7.swift b/ios/MullvadSettings/TunnelSettingsV7.swift index de9e9e98b294..ee83a3e40e2f 100644 --- a/ios/MullvadSettings/TunnelSettingsV7.swift +++ b/ios/MullvadSettings/TunnelSettingsV7.swift @@ -65,10 +65,8 @@ public struct TunnelSettingsV7: Codable, Equatable, TunnelSettings, Sendable { self.daita = try container.decode(DAITASettings.self, forKey: .daita) self.includeAllNetworks = - try container.decodeIfPresent( - IncludeAllNetworksSettings.self, - forKey: .includeAllNetworks - ) ?? IncludeAllNetworksSettings() + (try? container.decode(IncludeAllNetworksSettings.self, forKey: .includeAllNetworks)) + ?? IncludeAllNetworksSettings() } public func upgradeToNextVersion() -> any TunnelSettings { diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift index f1b4d65b6a71..69f45698626d 100644 --- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift @@ -256,6 +256,56 @@ final class MigrationManagerTests: XCTestCase, @unchecked Sendable { XCTAssertEqual(osakaRelayConstraints, latestSettings.relayConstraints) } + /// Settings serialized by ios/2026.1-build5 had `includeAllNetworks` and `localNetworkSharing` + /// as Bool fields in TunnelSettingsV7. The IAN activation flow changed `includeAllNetworks` + /// to an `IncludeAllNetworksSettings` struct (absorbing `localNetworkSharing`) without bumping + /// the schema version, so existing V7 data must still deserialize correctly. + func testDeserializationOfV7SettingsFromBuild5() throws { + // Verbatim representation of default V7 settings as written by ios/2026.1-build5. + let oldSettingsJSON = Data( + """ + { + "version": 7, + "data": { + "relayConstraints": { + "location": {"only": ["se"]}, + "locations": {"only": {"locations": [["se"]]}}, + "entryLocations": {"only": {"locations": [["se"]]}}, + "exitLocations": {"only": {"locations": [["se"]]}}, + "port": "any", + "filter": "any" + }, + "dnsSettings": { + "blockingOptions": 0, + "enableCustomDNS": false, + "customDNSDomains": [] + }, + "wireGuardObfuscation": { + "port": 0, + "state": {"automatic": {}}, + "udpOverTcpPort": {"automatic": {}}, + "shadowsocksPort": {"automatic": {}} + }, + "tunnelQuantumResistance": {"automatic": {}}, + "tunnelMultihopState": {"off": {}}, + "daita": { + "state": {"off": {}}, + "daitaState": {"off": {}}, + "directOnlyState": {"off": {}} + }, + "localNetworkSharing": true, + "includeAllNetworks": true + } + } + """.utf8) + + let parser = SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder()) + let settings = try parser.parsePayload(as: TunnelSettingsV7.self, from: oldSettingsJSON) + + XCTAssertFalse(settings.includeAllNetworks.includeAllNetworksIsEnabled) + XCTAssertFalse(settings.includeAllNetworks.localNetworkSharingIsEnabled) + } + private func migrateToLatest(_ settings: any TunnelSettings, version: SchemaVersion) throws { let store = Self.store try write(settings: settings, version: version.rawValue, in: store)