Skip to content

Commit

Permalink
Update the UI to indicate PQ state
Browse files Browse the repository at this point in the history
  • Loading branch information
acb-mv committed May 7, 2024
1 parent 77a28c0 commit c90da37
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 242 deletions.
7 changes: 7 additions & 0 deletions ios/MullvadSettings/QuantumResistanceSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ public enum TunnelQuantumResistance: Codable {
case on
case off
}

public extension TunnelQuantumResistance {
/// A single source of truth for whether the current state counts as on
var isEnabled: Bool {
self == .on
}
}
10 changes: 10 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
449EBA262B975B9700DFA4EB /* PostQuantumKeyReceiving.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */; };
44B02E3B2BC5732D008EDF34 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */; };
44B02E3C2BC5B8A5008EDF34 /* Bundle+ProductVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5891BF1B25E3E3EB006D6FB0 /* Bundle+ProductVersion.swift */; };
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F962BE527F4002520EB /* TunnelState+UI.swift */; };
44BB5F9A2BE529FF002520EB /* TunnelStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BB5F992BE529FE002520EB /* TunnelStateTests.swift */; };
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */; };
44DD7D272B6D18FB0005F67F /* MockTunnelInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */; };
44DD7D292B7113CA0005F67F /* MockTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DD7D282B7113CA0005F67F /* MockTunnel.swift */; };
Expand Down Expand Up @@ -1379,6 +1382,8 @@
449EB9FE2B95FF2500DFA4EB /* AccountMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMock.swift; sourceTree = "<group>"; };
449EBA252B975B9700DFA4EB /* PostQuantumKeyReceiving.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostQuantumKeyReceiving.swift; sourceTree = "<group>"; };
44B02E3A2BC5732D008EDF34 /* LoggingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
44BB5F962BE527F4002520EB /* TunnelState+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelState+UI.swift"; sourceTree = "<group>"; };
44BB5F992BE529FE002520EB /* TunnelStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelStateTests.swift; sourceTree = "<group>"; };
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTunnelOperationTests.swift; sourceTree = "<group>"; };
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnelInteractor.swift; sourceTree = "<group>"; };
44DD7D282B7113CA0005F67F /* MockTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2483,6 +2488,7 @@
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */,
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */,
A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */,
44BB5F992BE529FE002520EB /* TunnelStateTests.swift */,
A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */,
A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */,
58165EBD2A262CBB00688EAD /* WgKeyRotationTests.swift */,
Expand Down Expand Up @@ -2596,6 +2602,7 @@
5820676326E771DB00655B05 /* TunnelManagerErrors.swift */,
5823FA5326CE49F600283BF8 /* TunnelObserver.swift */,
58B93A1226C3F13600A55733 /* TunnelState.swift */,
44BB5F962BE527F4002520EB /* TunnelState+UI.swift */,
5803B4B12940A48700C23744 /* TunnelStore.swift */,
5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */,
58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */,
Expand Down Expand Up @@ -5199,6 +5206,7 @@
A9A5F9FE2ACB05160083449F /* NotificationManager.swift in Sources */,
A9A5F9FF2ACB05160083449F /* NotificationManagerDelegate.swift in Sources */,
7A9BE5AD2B90DF2D00E2A7D0 /* AllLocationsDataSourceTests.swift in Sources */,
44BB5F9A2BE529FF002520EB /* TunnelStateTests.swift in Sources */,
A900E9BE2ACC654100C95F67 /* APIProxy+Stubs.swift in Sources */,
A900E9BA2ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift in Sources */,
A9A5FA002ACB05160083449F /* ProductsRequestOperation.swift in Sources */,
Expand Down Expand Up @@ -5267,6 +5275,7 @@
7A9BE5AB2B909A1700E2A7D0 /* LocationDataSourceProtocol.swift in Sources */,
A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */,
44DD7D242B6CFFD70005F67F /* StartTunnelOperationTests.swift in Sources */,
44BB5F982BE527F4002520EB /* TunnelState+UI.swift in Sources */,
A9A5FA2B2ACB05160083449F /* CustomDateComponentsFormattingTests.swift in Sources */,
A9A5FA2C2ACB05160083449F /* DeviceCheckOperationTests.swift in Sources */,
A9A5FA2D2ACB05160083449F /* DurationTests.swift in Sources */,
Expand Down Expand Up @@ -5444,6 +5453,7 @@
7A9CCCB72A96302800DD6A34 /* RevokedCoordinator.swift in Sources */,
7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */,
587D96742886D87C00CD8F1C /* DeviceManagementContentView.swift in Sources */,
44BB5F972BE527F4002520EB /* TunnelState+UI.swift in Sources */,
7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */,
5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */,
5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,16 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
guard let selectedRelay = selectedRelay else { return }

do {
let settings = try SettingsManager.readSettings()
observedState = .connected(
ObservedConnectionState(
selectedRelay: selectedRelay,
relayConstraints: try SettingsManager.readSettings().relayConstraints,
relayConstraints: settings.relayConstraints,
networkReachability: .reachable,
connectionAttemptCount: 0,
transportLayer: .udp,
remotePort: selectedRelay.endpoint.ipv4Relay.port
remotePort: selectedRelay.endpoint.ipv4Relay.port,
isPostQuantum: settings.tunnelQuantumResistance.isEnabled
)
)
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ class MapConnectionStatusOperation: AsyncOperation {
switch observedState {
case let .connected(connectionState):
return connectionState.isNetworkReachable
? .connected(connectionState.selectedRelay)
? .connected(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .connecting(connectionState):
return connectionState.isNetworkReachable
? .connecting(connectionState.selectedRelay)
? .connecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .negotiatingPostQuantumKey(connectionState, privateKey):
return connectionState.isNetworkReachable
? .negotiatingPostQuantumKey(connectionState.selectedRelay, privateKey)
: .waitingForConnectivity(.noConnection)
case let .reconnecting(connectionState):
return connectionState.isNetworkReachable
? .reconnecting(connectionState.selectedRelay)
? .reconnecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .error(blockedState):
return .error(blockedState.reason)
Expand Down
5 changes: 4 additions & 1 deletion ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ class StartTunnelOperation: ResultOperation<Void> {

interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus = TunnelStatus()
tunnelStatus.state = .connecting(selectedRelay)
tunnelStatus.state = .connecting(
selectedRelay,
isPostQuantum: interactor.settings.tunnelQuantumResistance.isEnabled
)
}

try tunnel.start(options: tunnelOptions.rawOptions())
Expand Down
260 changes: 260 additions & 0 deletions ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
//
// TunnelState+UI.swift
// MullvadVPN
//
// Created by Andrew Bulhak on 2024-05-03.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import UIKit

extension TunnelState {
var textColorForSecureLabel: UIColor {
switch self {
case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingPostQuantumKey:
.white

case .connected:
.successColor

case .disconnecting, .disconnected, .pendingReconnect, .waitingForConnectivity(.noNetwork), .error:
.dangerColor
}
}

var shouldEnableButtons: Bool {
if case .waitingForConnectivity(.noNetwork) = self {
return false
}

return true
}

var localizedTitleForSecureLabel: String {
switch self {
case let .connecting(_, isPostQuantum), let .reconnecting(_, isPostQuantum):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTING",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTING",
tableName: "Main",
value: "Creating secure connection",
comment: ""
)
}

case .negotiatingPostQuantumKey:
NSLocalizedString(
"TUNNEL_STATE_NEGOTIATING_KEY",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)

case let .connected(_, isPostQuantum):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTED",
tableName: "Main",
value: "Quantum secure connection",
comment: ""
)
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTED",
tableName: "Main",
value: "Secure connection",
comment: ""
)
}

case .disconnecting(.nothing):
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTING",
tableName: "Main",
value: "Disconnecting",
comment: ""
)
case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"TUNNEL_STATE_PENDING_RECONNECT",
tableName: "Main",
value: "Reconnecting",
comment: ""
)

case .disconnected:
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTED",
tableName: "Main",
value: "Unsecured connection",
comment: ""
)

case .waitingForConnectivity(.noConnection), .error:
NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY",
tableName: "Main",
value: "Blocked connection",
comment: ""
)

case .waitingForConnectivity(.noNetwork):
NSLocalizedString(
"TUNNEL_STATE_NO_NETWORK",
tableName: "Main",
value: "No network",
comment: ""
)
}
}

var localizedTitleForSelectLocationButton: String? {
switch self {
case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Select location",
comment: ""
)

case .disconnected, .disconnecting(.nothing):
NSLocalizedString(
"SELECT_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Select location",
comment: ""
)

case .connecting, .connected, .reconnecting, .waitingForConnectivity, .error:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Switch location",
comment: ""
)

case .negotiatingPostQuantumKey:
NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Switch location",
comment: ""
)
}
}

var localizedAccessibilityLabel: String {
switch self {
case let .connecting(_, isPostQuantum):
if isPostQuantum {
NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)
} else {
NSLocalizedString(
"TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Creating secure connection",
comment: ""
)
}

// TODO: Is this correct ?
case .negotiatingPostQuantumKey:
NSLocalizedString(
"TUNNEL_STATE_CONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Creating quantum secure connection",
comment: ""
)

case let .connected(tunnelInfo, isPostQuantum):
if isPostQuantum {
String(
format: NSLocalizedString(
"TUNNEL_STATE_PQ_CONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Quantum secure connection. Connected to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
)
} else {
String(
format: NSLocalizedString(
"TUNNEL_STATE_CONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Secure connection. Connected to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
)
}

case .disconnected:
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTED_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Unsecured connection",
comment: ""
)

case let .reconnecting(tunnelInfo, _):
String(
format: NSLocalizedString(
"TUNNEL_STATE_RECONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Reconnecting to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
)

case .waitingForConnectivity(.noConnection), .error:
NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Blocked connection",
comment: ""
)

case .waitingForConnectivity(.noNetwork):
NSLocalizedString(
"TUNNEL_STATE_NO_NETWORK_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "No network",
comment: ""
)

case .disconnecting(.nothing):
NSLocalizedString(
"TUNNEL_STATE_DISCONNECTING_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Disconnecting",
comment: ""
)

case .disconnecting(.reconnect), .pendingReconnect:
NSLocalizedString(
"TUNNEL_STATE_PENDING_RECONNECT_ACCESSIBILITY_LABEL",
tableName: "Main",
value: "Reconnecting",
comment: ""
)
}
}
}
Loading

0 comments on commit c90da37

Please sign in to comment.