Skip to content
Open
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
1 change: 1 addition & 0 deletions ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Line wrap the file at 100 chars. Th
- Show disabled servers in location view.
- Add ability which types of local notifications are delivered.
- Remove invalid Shadowsocks ciphers.
- Remove Automatic quantum-resistant tunnel option

[TunnelCrack]: https://tunnelcrack.mathyvanhoef.com/

Expand Down
39 changes: 37 additions & 2 deletions ios/MullvadSettings/QuantumResistanceSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,49 @@
import Foundation

public enum TunnelQuantumResistance: Codable, Sendable {
case automatic
case on
case off

private enum CodingKeys: String, CodingKey {
case automatic, on, off
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.automatic) {
self = .on
return
}

if container.contains(.on) {
self = .on
return
}

if container.contains(.off) {
self = .off
return
}

self = .on
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

switch self {
case .on:
try container.encode([String: String](), forKey: .on)

case .off:
try container.encode([String: String](), forKey: .off)
}
}
}

public extension TunnelQuantumResistance {
/// A single source of truth for whether the current state counts as on
var isEnabled: Bool {
[.on, .automatic].contains(self)
self == .on
}
}
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV3.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public struct TunnelSettingsV3: Codable, Equatable, TunnelSettings {
relayConstraints: relayConstraints,
dnsSettings: dnsSettings,
wireGuardObfuscation: wireGuardObfuscation,
tunnelQuantumResistance: .automatic
tunnelQuantumResistance: .on
)
}

Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct TunnelSettingsV4: Codable, Equatable, TunnelSettings {
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic
tunnelQuantumResistance: TunnelQuantumResistance = .on
) {
self.relayConstraints = relayConstraints
self.dnsSettings = dnsSettings
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV5.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public struct TunnelSettingsV5: Codable, Equatable, TunnelSettings {
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelQuantumResistance: TunnelQuantumResistance = .on,
tunnelMultihopState: MultihopStateV1 = .off

) {
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV6.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct TunnelSettingsV6: Codable, Equatable, TunnelSettings, Sendable {
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelQuantumResistance: TunnelQuantumResistance = .on,
tunnelMultihopState: MultihopStateV1 = .off,
daita: DAITASettings = DAITASettings()
) {
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV7.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct TunnelSettingsV7: Codable, Equatable, TunnelSettings, Sendable {
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelQuantumResistance: TunnelQuantumResistance = .on,
tunnelMultihopState: MultihopStateV1 = .off,
daita: DAITASettings = DAITASettings(),
includeAllNetworks: IncludeAllNetworksSettings = IncludeAllNetworksSettings()
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/TunnelSettingsV8.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct TunnelSettingsV8: Codable, Equatable, TunnelSettings, Sendable {
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelQuantumResistance: TunnelQuantumResistance = .on,
tunnelMultihopState: MultihopStateV2 = .never,
daita: DAITASettings = DAITASettings(),
includeAllNetworks: IncludeAllNetworksSettings = IncludeAllNetworksSettings()
Expand Down
1 change: 0 additions & 1 deletion ios/MullvadVPN/Classes/AccessbilityIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ public enum AccessibilityIdentifier: Equatable {
case localNetworkSharingSwitch

// Quantum resistance
case quantumResistanceAutomatic
case quantumResistanceOff
case quantumResistanceOn

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,6 @@ final class VPNSettingsCellFactory: @preconcurrency CellFactoryProtocol {
cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.applySubCellStyling()

case .quantumResistanceAutomatic:
guard let cell = cell as? SelectableSettingsCell else { return }

cell.titleLabel.text = NSLocalizedString("Automatic", comment: "")
cell.setAccessibilityIdentifier(item.accessibilityIdentifier)
cell.applySubCellStyling()

case .quantumResistanceOn:
guard let cell = cell as? SelectableSettingsCell else { return }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case wireGuardObfuscationQuic
case wireGuardObfuscationLwo
case wireGuardObfuscationOff
case quantumResistanceAutomatic
case quantumResistanceOn
case quantumResistanceOff

Expand All @@ -99,7 +98,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
}

static var quantumResistance: [Item] {
[.quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff]
[.quantumResistanceOn, .quantumResistanceOff]
}

var accessibilityIdentifier: AccessibilityIdentifier {
Expand All @@ -124,8 +123,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
.wireGuardObfuscationLwo
case .wireGuardObfuscationOff:
.wireGuardObfuscationOff
case .quantumResistanceAutomatic:
.quantumResistanceAutomatic
case .quantumResistanceOn:
.quantumResistanceOn
case .quantumResistanceOff:
Expand All @@ -147,7 +144,7 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
.wireGuardObfuscation
case .wireGuardObfuscationUdpOverTcp, .wireGuardObfuscationShadowsocks, .wireGuardObfuscationLwo:
.wireGuardObfuscationOption
case .quantumResistanceAutomatic, .quantumResistanceOn, .quantumResistanceOff:
case .quantumResistanceOn, .quantumResistanceOff:
.quantumResistance
}
}
Expand Down Expand Up @@ -192,7 +189,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<

let quantumResistanceItem: Item =
switch viewModel.quantumResistance {
case .automatic: .quantumResistanceAutomatic
case .off: .quantumResistanceOff
case .on: .quantumResistanceOn
}
Expand Down Expand Up @@ -331,9 +327,6 @@ final class VPNSettingsDataSource: UITableViewDiffableDataSource<
case .wireGuardObfuscationOff:
selectObfuscationState(.off)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.obfuscation(obfuscationSettings))
case .quantumResistanceAutomatic:
selectQuantumResistance(.automatic)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.quantumResistance(viewModel.quantumResistance))
case .quantumResistanceOn:
selectQuantumResistance(.on)
delegate?.didUpdateTunnelSettings(TunnelSettingsUpdate.quantumResistance(viewModel.quantumResistance))
Expand Down
54 changes: 54 additions & 0 deletions ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,60 @@ final class MigrationManagerTests: XCTestCase, @unchecked Sendable {
XCTAssertFalse(settings.includeAllNetworks.localNetworkSharingIsEnabled)
}

/// Migration test: ensures that previously stored settings using the removed
/// `automatic` case for `tunnelQuantumResistance` are safely mapped to `.on`.
/// Prevents crashes and guarantees consistent behavior for existing users
/// after upgrading to versions where `automatic` no longer exists.
func testTunnelQuantumResistanceMigratesAutomaticToOn() throws {

let oldSettingsJSON = Data(
"""
{
"version": 4,
"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": {}}
}
}
""".utf8)

let store = Self.store
let parser = SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
let tunnelSettingsV4 = try parser.parsePayload(as: TunnelSettingsV4.self, from: oldSettingsJSON)
try write(settings: tunnelSettingsV4, version: 4, in: store)

let successfulMigrationExpectation = expectation(description: "Successful migration")
manager.migrateSettings(store: store) { result in
if case .success = result {
successfulMigrationExpectation.fulfill()
}
}
wait(for: [successfulMigrationExpectation], timeout: .UnitTest.timeout)

let latestSettingsData = try XCTUnwrap(store.read(key: .settings))
let latestSettings = try parser.parsePayload(as: LatestTunnelSettings.self, from: latestSettingsData)

XCTAssertEqual(latestSettings.tunnelQuantumResistance, .on)
}

private func migrateToLatest(_ settings: any TunnelSettings, version: SchemaVersion) throws {
let store = Self.store
try write(settings: settings, version: version.rawValue, in: store)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ final class TunnelSettingsUpdateTests: XCTestCase {
XCTAssertTrue(settings.tunnelQuantumResistance.isEnabled)

// When again:
update = TunnelSettingsUpdate.quantumResistance(.automatic)
update = TunnelSettingsUpdate.quantumResistance(.on)
update.apply(to: &settings)

// Then again:
Expand Down
6 changes: 0 additions & 6 deletions ios/MullvadVPNUITests/Pages/VPNSettingsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,6 @@ class VPNSettingsPage: Page {
return self
}

@discardableResult func tapQuantumResistantTunnelAutomaticCell() -> Self {
app.cells[AccessibilityIdentifier.quantumResistanceAutomatic]
.tap()
return self
}

@discardableResult func tapQuantumResistantTunnelOnCell() -> Self {
app.cells[AccessibilityIdentifier.quantumResistanceOn]
.tap()
Expand Down
2 changes: 1 addition & 1 deletion ios/PacketTunnelCoreTests/Mocks/SettingsReaderStub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension SettingsReaderStub {
relayConstraints: RelayConstraints(),
dnsSettings: DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings(state: .off),
tunnelQuantumResistance: .automatic,
tunnelQuantumResistance: .on,
tunnelMultihopState: .never,
daita: DAITASettings()
)
Expand Down
Loading