Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ios/MullvadSettings/TunnelSettingsUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 2 additions & 4 deletions ios/MullvadSettings/TunnelSettingsV7.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading