summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-05-27 14:55:05 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-05-27 14:55:05 +0200
commit0fc586eb732c3a55769069645312de9f42416d82 (patch)
treeb094a5602e0c7d14f25257c21320b1b6cea5ae51
parent6c9f7ec18da5bd26ce5880e5dc8f5c8b83d5f77f (diff)
parentdc759cbb35009ccede6fc5eab9b404525eac12fb (diff)
downloadmullvadvpn-0fc586eb732c3a55769069645312de9f42416d82.tar.xz
mullvadvpn-0fc586eb732c3a55769069645312de9f42416d82.zip
Merge branch 'swift-6-packet-tunnel'
-rw-r--r--ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift5
-rw-r--r--ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift28
-rw-r--r--ios/MullvadRustRuntimeTests/TCPConnection.swift2
-rw-r--r--ios/MullvadRustRuntimeTests/UDPConnection.swift2
-rw-r--r--ios/MullvadTypes/Promise.swift2
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj56
-rw-r--r--ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift4
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift5
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift50
-rw-r--r--ios/MullvadVPNUITests/Base/BaseUITestCase.swift12
-rw-r--r--ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift8
-rw-r--r--ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift4
-rw-r--r--ios/MullvadVPNUITests/Networking/FirewallClient.swift8
-rw-r--r--ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift10
-rw-r--r--ios/MullvadVPNUITests/Networking/Networking.swift10
-rw-r--r--ios/MullvadVPNUITests/Networking/PacketCapture.swift6
-rw-r--r--ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift4
-rw-r--r--ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift4
-rw-r--r--ios/MullvadVPNUITests/Networking/TrafficGenerator.swift4
-rw-r--r--ios/MullvadVPNUITests/Pages/LoginPage.swift14
-rw-r--r--ios/MullvadVPNUITests/Pages/Page.swift1
-rw-r--r--ios/MullvadVPNUITests/RelayTests.swift8
-rw-r--r--ios/MullvadVPNUITests/SettingsMigrationTests.swift4
-rw-r--r--ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift6
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift2
-rw-r--r--ios/PacketTunnelCore/Pinger/PingerProtocol.swift2
-rw-r--r--ios/PacketTunnelCore/Pinger/TunnelPinger.swift2
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift2
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift3
-rw-r--r--ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift2
-rw-r--r--ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift2
-rw-r--r--ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift20
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift2
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift4
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PingerMock.swift2
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift2
-rw-r--r--ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift16
-rw-r--r--ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift2
-rw-r--r--ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift12
-rw-r--r--ios/PacketTunnelCoreTests/TunnelMonitorTests.swift8
45 files changed, 158 insertions, 192 deletions
diff --git a/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift b/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift
index efc8c0fe0d..82e0ebdb48 100644
--- a/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift
+++ b/ios/MullvadRustRuntimeTests/EphemeralPeerExchangeActorTests.swift
@@ -15,12 +15,10 @@ import NetworkExtension
import XCTest
class EphemeralPeerExchangeActorTests: XCTestCase {
- var tcpConnection: NWTCPConnectionStub!
var tunnelProvider: TunnelProviderStub!
override func setUpWithError() throws {
- tcpConnection = NWTCPConnectionStub()
- tunnelProvider = TunnelProviderStub(tcpConnection: tcpConnection)
+ tunnelProvider = TunnelProviderStub()
}
func testKeyExchangeFailsWhenNegotiationCannotStart() {
@@ -37,7 +35,6 @@ class EphemeralPeerExchangeActorTests: XCTestCase {
let privateKey = PrivateKey()
keyExchangeActor.startNegotiation(with: privateKey, enablePostQuantum: true, enableDaita: false)
- tcpConnection.becomeViable()
wait(for: [negotiationFailure])
}
diff --git a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
index 59805230be..a62771003e 100644
--- a/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
+++ b/ios/MullvadRustRuntimeTests/MullvadPostQuantum+Stubs.swift
@@ -12,19 +12,6 @@ import NetworkExtension
@testable import PacketTunnelCore
@testable import WireGuardKitTypes
-class NWTCPConnectionStub: NWTCPConnection {
- var _isViable = false
- override var isViable: Bool {
- _isViable
- }
-
- func becomeViable() {
- willChangeValue(for: \.isViable)
- _isViable = true
- didChangeValue(for: \.isViable)
- }
-}
-
class TunnelProviderStub: TunnelProvider {
func tunnelHandle() throws -> Int32 {
0
@@ -38,21 +25,6 @@ class TunnelProviderStub: TunnelProvider {
send: { _, _, _, _ in return 0 }
)
}
-
- let tcpConnection: NWTCPConnectionStub
-
- init(tcpConnection: NWTCPConnectionStub) {
- self.tcpConnection = tcpConnection
- }
-
- func createTCPConnectionThroughTunnel(
- to remoteEndpoint: NWEndpoint,
- enableTLS: Bool,
- tlsParameters TLSParameters: NWTLSParameters?,
- delegate: Any?
- ) -> NWTCPConnection {
- tcpConnection
- }
}
class FailedNegotiatorStub: EphemeralPeerNegotiating {
diff --git a/ios/MullvadRustRuntimeTests/TCPConnection.swift b/ios/MullvadRustRuntimeTests/TCPConnection.swift
index cffce29e4e..c4c74ce76a 100644
--- a/ios/MullvadRustRuntimeTests/TCPConnection.swift
+++ b/ios/MullvadRustRuntimeTests/TCPConnection.swift
@@ -11,7 +11,7 @@ import Network
/// Minimal implementation of TCP connection capable of receiving data.
/// > Warning: Do not use this implementation in production code. See the warning in `start()`.
-class TCPConnection: Connection {
+class TCPConnection: Connection, @unchecked Sendable {
private let dispatchQueue = DispatchQueue(label: "TCPConnection")
private let nwConnection: NWConnection
diff --git a/ios/MullvadRustRuntimeTests/UDPConnection.swift b/ios/MullvadRustRuntimeTests/UDPConnection.swift
index 66ef178d96..c4b3becc1d 100644
--- a/ios/MullvadRustRuntimeTests/UDPConnection.swift
+++ b/ios/MullvadRustRuntimeTests/UDPConnection.swift
@@ -16,7 +16,7 @@ protocol Connection {
/// Minimal implementation of UDP connection capable of sending data.
/// > Warning: Do not use this implementation in production code. See the warning in `start()`.
-class UDPConnection: Connection {
+class UDPConnection: Connection, @unchecked Sendable {
private let dispatchQueue = DispatchQueue(label: "UDPConnection")
private let nwConnection: NWConnection
diff --git a/ios/MullvadTypes/Promise.swift b/ios/MullvadTypes/Promise.swift
index b5f5a58873..3bf66e7ae3 100644
--- a/ios/MullvadTypes/Promise.swift
+++ b/ios/MullvadTypes/Promise.swift
@@ -52,7 +52,7 @@ public final class Promise<Success, Failure: Error>: @unchecked Sendable {
// allows the waiter to wait to `receive()` from another operation
// asynchronously. It is important not to forget to call `send`, otherwise this
// operation will block indefinitely.
-public struct OneshotChannel {
+public struct OneshotChannel: Sendable {
private var continuation: AsyncStream<Void>.Continuation?
private var stream: AsyncStream<Void>
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index de1e60d6b1..eb7a32dcd8 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -7338,7 +7338,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -7358,7 +7358,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -7469,7 +7469,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -7509,7 +7509,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -7532,7 +7532,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -7553,7 +7553,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -7755,7 +7755,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = Debug;
};
@@ -7775,7 +7775,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APPLICATION_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = minimal;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = Release;
};
@@ -8230,7 +8230,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG MULLVAD_ENVIRONMENT_PRODUCTION $(inherited)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MullvadVPN;
};
@@ -8262,7 +8262,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MullvadVPN;
};
@@ -8396,7 +8396,7 @@
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = minimal;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = Staging;
};
@@ -8415,7 +8415,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -8634,7 +8634,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8657,7 +8657,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -8789,7 +8789,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG MULLVAD_ENVIRONMENT_STAGING";
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MullvadVPN;
};
@@ -8813,7 +8813,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = MULLVAD_ENVIRONMENT_PRODUCTION;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/MullvadVPNUITests/BridgingHeader.h";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = MullvadVPN;
};
@@ -8864,7 +8864,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8915,7 +8915,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -8966,7 +8966,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -9016,7 +9016,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -9044,7 +9044,7 @@
SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -9069,7 +9069,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Staging;
@@ -9094,7 +9094,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -9119,7 +9119,7 @@
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
@@ -9242,7 +9242,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Packet Tunnel Development";
SWIFT_STRICT_CONCURRENCY = minimal;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
};
name = MockRelease;
};
@@ -9261,7 +9261,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
@@ -9480,7 +9480,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -9503,7 +9503,7 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
+ SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = MockRelease;
diff --git a/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift b/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift
index 87f215dd33..0ff1ab8df8 100644
--- a/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift
+++ b/ios/MullvadVPNTests/MullvadLogging/LogFileOutputStreamTests.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Foundation
+@preconcurrency import Foundation
@testable import MullvadLogging
import Testing
diff --git a/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift b/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift
index 6bd16f5432..36d60bf4f7 100644
--- a/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/IPOverrideRepositoryStub.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Combine
+@preconcurrency import Combine
import MullvadSettings
struct IPOverrideRepositoryStub: IPOverrideRepositoryProtocol {
diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
index 6e619256c5..d268171d99 100644
--- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerMultiProcessUpgradeTests.swift
@@ -26,8 +26,8 @@ extension MigrationManagerTests {
let backgroundMigrationExpectation = expectation(description: "Migration from packet tunnel")
let foregroundMigrationExpectation = expectation(description: "Migration from host")
- var migrationHappenedInPacketTunnel = false
- var migrationHappenedInHost = false
+ nonisolated(unsafe) var migrationHappenedInPacketTunnel = false
+ nonisolated(unsafe) var migrationHappenedInHost = false
packetTunnelProcess.async { [unowned self] in
manager.migrateSettings(store: MigrationManagerTests.store) { backgroundMigrationResult in
diff --git a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
index f62ca74403..ab93a2d37d 100644
--- a/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/MigrationManagerTests.swift
@@ -12,7 +12,7 @@
@testable import MullvadTypes
import XCTest
-final class MigrationManagerTests: XCTestCase {
+final class MigrationManagerTests: XCTestCase, @unchecked Sendable {
static let store = InMemorySettingsStore<SettingNotFound>()
var manager: MigrationManager!
diff --git a/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift b/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift
index def507f6a7..fb77a6f5e9 100644
--- a/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/Classes/InputTextFormatterTests.swift
@@ -8,6 +8,7 @@
import XCTest
+@MainActor
class InputTextFormatterTests: XCTestCase {
private let accountNumber = "12345678"
private var inputTextFormatter: InputTextFormatter!
@@ -19,14 +20,14 @@ class InputTextFormatterTests: XCTestCase {
maxGroups: 4
)
- override func setUp() {
+ override func setUp() async throws {
inputTextFormatter = InputTextFormatter(
string: accountNumber,
configuration: configuration
)
}
- override func tearDown() {
+ override func tearDown() async throws {
inputTextFormatter = nil
}
diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift
index fecc0f384b..8a5f95cb15 100644
--- a/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnel/DeviceCheck/DeviceCheckOperationTests.swift
@@ -12,14 +12,14 @@ import MullvadSettings
import MullvadTypes
import Operations
import PacketTunnelCore
-import WireGuardKitTypes
+@preconcurrency import WireGuardKitTypes
import XCTest
class DeviceCheckOperationTests: XCTestCase {
private let operationQueue = AsyncOperationQueue()
private let dispatchQueue = DispatchQueue(label: "TestQueue")
- func testShouldReportExpiredAccount() {
+ func testShouldReportExpiredAccount() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -45,10 +45,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldNotRotateKeyForInvalidAccount() {
+ func testShouldNotRotateKeyForInvalidAccount() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -76,10 +76,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldNotRotateKeyForRevokedDevice() {
+ func testShouldNotRotateKeyForRevokedDevice() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -107,10 +107,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldRotateKeyOnMismatchImmediately() {
+ func testShouldRotateKeyOnMismatchImmediately() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -136,10 +136,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldRespectCooldownWhenAttemptingToRotateImmediately() {
+ func testShouldRespectCooldownWhenAttemptingToRotateImmediately() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -165,10 +165,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldNotRotateDeviceKeyWhenServerKeyIsIdentical() {
+ func testShouldNotRotateDeviceKeyWhenServerKeyIsIdentical() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -188,10 +188,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldNotRotateKeyBeforeRetryIntervalPassed() {
+ func testShouldNotRotateKeyBeforeRetryIntervalPassed() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -213,10 +213,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldRotateKeyOnceInTwentyFourHours() {
+ func testShouldRotateKeyOnceInTwentyFourHours() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -238,10 +238,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldReportFailedKeyRotationAttempt() {
+ func testShouldReportFailedKeyRotationAttempt() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -268,10 +268,10 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
- func testShouldFailOnKeyRotationRace() {
+ func testShouldFailOnKeyRotationRace() async {
let expect = expectation(description: "Wait for operation to complete")
let currentKey = PrivateKey()
@@ -305,7 +305,7 @@ class DeviceCheckOperationTests: XCTestCase {
expect.fulfill()
}
- waitForExpectations(timeout: .UnitTest.timeout)
+ await fulfillment(of: [expect], timeout: .UnitTest.timeout)
}
private func startDeviceCheck(
@@ -327,7 +327,7 @@ class DeviceCheckOperationTests: XCTestCase {
}
/// Mock implementation of a remote service used by `DeviceCheckOperation` to reach the API.
-private class MockRemoteService: DeviceCheckRemoteServiceProtocol {
+private class MockRemoteService: DeviceCheckRemoteServiceProtocol, @unchecked Sendable {
typealias AccountDataHandler = (_ accountNumber: String) throws -> Account
typealias DeviceDataHandler = (_ accountNumber: String, _ deviceIdentifier: String) throws -> Device
typealias RotateDeviceKeyHandler = (
@@ -356,7 +356,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol {
func getAccountData(
accountNumber: String,
- completion: @escaping (Result<Account, Error>) -> Void
+ completion: @escaping @Sendable (Result<Account, Error>) -> Void
) -> Cancellable {
DispatchQueue.main.async { [self] in
let result: Result<Account, Error> = Result {
@@ -374,7 +374,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol {
func getDevice(
accountNumber: String,
identifier: String,
- completion: @escaping (Result<Device, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
) -> Cancellable {
DispatchQueue.main.async { [self] in
let result: Result<Device, Error> = Result {
@@ -395,7 +395,7 @@ private class MockRemoteService: DeviceCheckRemoteServiceProtocol {
accountNumber: String,
identifier: String,
publicKey: PublicKey,
- completion: @escaping (Result<Device, Error>) -> Void
+ completion: @escaping @Sendable (Result<Device, Error>) -> Void
) -> Cancellable {
DispatchQueue.main.async { [self] in
let result: Result<Device, Error> = Result {
diff --git a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
index d9265ff8ea..71fbbf0b05 100644
--- a/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
+++ b/ios/MullvadVPNUITests/Base/BaseUITestCase.swift
@@ -9,6 +9,7 @@
import Foundation
import XCTest
+@MainActor
class BaseUITestCase: XCTestCase {
let app = XCUIApplication()
static let defaultTimeout = 5.0
@@ -203,20 +204,23 @@ class BaseUITestCase: XCTestCase {
/// Suite level teardown ran after all tests in suite have been executed
override class func tearDown() {
- if shouldUninstallAppInTeardown() && uninstallAppInTestSuiteTearDown() {
- uninstallApp()
+ // This function is not marked `@MainActor` therefore cannot legally enter its context without help
+ Task { @MainActor in
+ if shouldUninstallAppInTeardown() && uninstallAppInTestSuiteTearDown() {
+ uninstallApp()
+ }
}
}
/// Test level setup
- override func setUp() {
+ override func setUp() async throws {
currentTestCaseShouldCapturePackets = false // Reset for each test case run
continueAfterFailure = false
app.launch()
}
/// Test level teardown
- override func tearDown() {
+ override func tearDown() async throws {
if currentTestCaseShouldCapturePackets {
guard let packetCaptureSession = packetCaptureSession else {
XCTFail("Packet capture session unexpectedly not set up")
diff --git a/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift b/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift
index 00fbd24ed0..72847d7e42 100644
--- a/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift
+++ b/ios/MullvadVPNUITests/Base/LoggedInWithTimeUITestCase.swift
@@ -13,8 +13,8 @@ import XCTest
class LoggedInWithTimeUITestCase: BaseUITestCase {
var hasTimeAccountNumber: String?
- override func setUp() {
- super.setUp()
+ override func setUp() async throws {
+ try await super.setUp()
agreeToTermsOfServiceIfShown()
// Make sure that if a previous test ended up in a state where the app got stuck connecting to a relay
@@ -35,8 +35,8 @@ class LoggedInWithTimeUITestCase: BaseUITestCase {
app.launch()
}
- override func tearDown() {
- super.tearDown()
+ override func tearDown() async throws {
+ try await super.tearDown()
guard let hasTimeAccountNumber = self.hasTimeAccountNumber else {
XCTFail("hasTimeAccountNumber unexpectedly not set")
diff --git a/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift b/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift
index 3414239dc5..9b00ee47ce 100644
--- a/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift
+++ b/ios/MullvadVPNUITests/Base/LoggedOutUITestCase.swift
@@ -10,8 +10,8 @@ import Foundation
/// Base class for tests which should start from a logged out state
class LoggedOutUITestCase: BaseUITestCase {
- override func setUp() {
- super.setUp()
+ override func setUp() async throws {
+ try await super.setUp()
agreeToTermsOfServiceIfShown()
logoutIfLoggedIn()
diff --git a/ios/MullvadVPNUITests/Networking/FirewallClient.swift b/ios/MullvadVPNUITests/Networking/FirewallClient.swift
index 35d8d25968..55bb35d03b 100644
--- a/ios/MullvadVPNUITests/Networking/FirewallClient.swift
+++ b/ios/MullvadVPNUITests/Networking/FirewallClient.swift
@@ -35,8 +35,8 @@ class FirewallClient: TestRouterAPIClient {
"protocols": firewallRule.protocolsAsStringArray(),
]
- var requestError: Error?
- var requestResponse: URLResponse?
+ nonisolated(unsafe) var requestError: Error?
+ nonisolated(unsafe) var requestResponse: URLResponse?
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
)
@@ -82,8 +82,8 @@ class FirewallClient: TestRouterAPIClient {
var request = URLRequest(url: removeRulesURL)
request.httpMethod = "DELETE"
- var requestResponse: URLResponse?
- var requestError: Error?
+ nonisolated(unsafe) var requestResponse: URLResponse?
+ nonisolated(unsafe) var requestError: Error?
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
)
diff --git a/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift
index 922fbabce0..9d5bd0d64b 100644
--- a/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift
+++ b/ios/MullvadVPNUITests/Networking/MullvadAPIWrapper.swift
@@ -15,7 +15,7 @@ enum MullvadAPIError: Error {
case requestError
}
-class MullvadAPIWrapper {
+class MullvadAPIWrapper: @unchecked Sendable {
private var mullvadAPI: MullvadApi
private let throttleQueue = DispatchQueue(label: "MullvadAPIWrapperThrottleQueue", qos: .userInitiated)
private var lastCallDate: Date?
@@ -38,7 +38,7 @@ class MullvadAPIWrapper {
}
/// Throttle what's in the callback. This is used for throttling requests to the app API. All requests should be throttled or else we might be rate limited. The API allows maximum 5 requests per second. Note that the implementation assumes what is being throttled is synchronous.
- private func throttle(callback: @escaping () -> Void) {
+ private func throttle(callback: @Sendable @escaping () -> Void) {
throttleQueue.async {
let now = Date()
var delay: TimeInterval = 0
@@ -85,7 +85,7 @@ class MullvadAPIWrapper {
}
func createAccount() -> String {
- var accountNumber = String()
+ nonisolated(unsafe) var accountNumber = String()
let requestCompletedExpectation = XCTestExpectation(description: "Create account request completed")
throttle {
@@ -150,7 +150,7 @@ class MullvadAPIWrapper {
}
func getAccountExpiry(_ account: String) throws -> Date {
- var accountExpiryDate: Date = .distantPast
+ nonisolated(unsafe) var accountExpiryDate: Date = .distantPast
let requestCompletedExpectation = XCTestExpectation(description: "Get account expiry request completed")
throttle {
@@ -171,7 +171,7 @@ class MullvadAPIWrapper {
}
func getDevices(_ account: String) throws -> [Device] {
- var devices: [Device] = []
+ nonisolated(unsafe) var devices: [Device] = []
let requestCompletedExpectation = XCTestExpectation(description: "Get devices request completed")
throttle {
diff --git a/ios/MullvadVPNUITests/Networking/Networking.swift b/ios/MullvadVPNUITests/Networking/Networking.swift
index df814f70c7..8ff3d8b836 100644
--- a/ios/MullvadVPNUITests/Networking/Networking.swift
+++ b/ios/MullvadVPNUITests/Networking/Networking.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import Network
+@preconcurrency import Network
import XCTest
enum TransportProtocol: Codable {
@@ -60,7 +60,7 @@ class Networking {
let socketHost = NWEndpoint.Host(host)
let socketPort = try XCTUnwrap(NWEndpoint.Port(port))
let connection = NWConnection(host: socketHost, port: socketPort, using: .tcp)
- var connectionError: Error?
+ nonisolated(unsafe) var connectionError: Error?
let connectionStateDeterminedExpectation = XCTestExpectation(
description: "Completion handler for the reach ad serving domain request is invoked"
@@ -173,9 +173,9 @@ class Networking {
var request = URLRequest(url: mullvadDNSLeakURL)
request.setValue("application/json", forHTTPHeaderField: "accept")
- var requestData: Data?
- var requestResponse: URLResponse?
- var requestError: Error?
+ nonisolated(unsafe) var requestData: Data?
+ nonisolated(unsafe) var requestResponse: URLResponse?
+ nonisolated(unsafe) var requestError: Error?
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
)
diff --git a/ios/MullvadVPNUITests/Networking/PacketCapture.swift b/ios/MullvadVPNUITests/Networking/PacketCapture.swift
index d47e8433ba..266177c8db 100644
--- a/ios/MullvadVPNUITests/Networking/PacketCapture.swift
+++ b/ios/MullvadVPNUITests/Networking/PacketCapture.swift
@@ -290,9 +290,9 @@ class PacketCaptureClient: TestRouterAPIClient {
}
}
- var requestResponse: URLResponse?
- var requestError: Error?
- var responseData: Data?
+ nonisolated(unsafe) var requestResponse: URLResponse?
+ nonisolated(unsafe) var requestError: Error?
+ nonisolated(unsafe) var responseData: Data?
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
diff --git a/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift b/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift
index 20bc525d13..819c10af60 100644
--- a/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift
+++ b/ios/MullvadVPNUITests/Networking/PartnerAPIClient.swift
@@ -65,7 +65,7 @@ class PartnerAPIClient {
request.httpMethod = method
request.setValue("Basic \(accessToken)", forHTTPHeaderField: "Authorization")
- var jsonResponse: [String: Any] = [:]
+ nonisolated(unsafe) var jsonResponse: [String: Any] = [:]
do {
if let jsonObject = jsonObject {
@@ -81,7 +81,7 @@ class PartnerAPIClient {
description: "Completion handler for the request is invoked"
)
- var requestError: Error?
+ nonisolated(unsafe) var requestError: Error?
let task = URLSession.shared.dataTask(with: request) { data, response, error in
requestError = error
diff --git a/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift b/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift
index 0d9c9da258..0035062269 100644
--- a/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift
+++ b/ios/MullvadVPNUITests/Networking/TestRouterAPIClient.swift
@@ -19,8 +19,8 @@ class TestRouterAPIClient {
let completionHandlerInvokedExpectation = XCTestExpectation(
description: "Completion handler for the request is invoked"
)
- var deviceIPAddress = ""
- var requestError: Error?
+ nonisolated(unsafe) var deviceIPAddress = ""
+ nonisolated(unsafe) var requestError: Error?
let dataTask = URLSession.shared.dataTask(with: request) { data, _, _ in
defer { completionHandlerInvokedExpectation.fulfill() }
diff --git a/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift b/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift
index 590246c5cc..99d4677235 100644
--- a/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift
+++ b/ios/MullvadVPNUITests/Networking/TrafficGenerator.swift
@@ -6,10 +6,10 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
-import Network
+@preconcurrency import Network
import XCTest
-class TrafficGenerator {
+class TrafficGenerator: @unchecked Sendable {
let destinationHost: String
let port: Int
var connection: NWConnection
diff --git a/ios/MullvadVPNUITests/Pages/LoginPage.swift b/ios/MullvadVPNUITests/Pages/LoginPage.swift
index 24d1adcf2c..e120637a52 100644
--- a/ios/MullvadVPNUITests/Pages/LoginPage.swift
+++ b/ios/MullvadVPNUITests/Pages/LoginPage.swift
@@ -60,14 +60,16 @@ class LoginPage: Page {
// Success icon is only shown very briefly, since another view is presented after success icon is shown.
// Therefore we need to poll faster than waitForElement function.
let successIconDisplayedExpectation = XCTestExpectation(description: "Success icon shown")
- var isShown = false
+ nonisolated(unsafe) var isShown = false
let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [self] _ in
- let statusImageView = self.app.images[.statusImageView]
+ DispatchQueue.main.async {
+ let statusImageView = self.app.images[.statusImageView]
- if statusImageView.exists {
- if statusImageView.value as? String == "success" {
- isShown = true
- successIconDisplayedExpectation.fulfill()
+ if statusImageView.exists {
+ if statusImageView.value as? String == "success" {
+ isShown = true
+ successIconDisplayedExpectation.fulfill()
+ }
}
}
}
diff --git a/ios/MullvadVPNUITests/Pages/Page.swift b/ios/MullvadVPNUITests/Pages/Page.swift
index c11ac71577..5cb281f70d 100644
--- a/ios/MullvadVPNUITests/Pages/Page.swift
+++ b/ios/MullvadVPNUITests/Pages/Page.swift
@@ -9,6 +9,7 @@
import Foundation
import XCTest
+@MainActor
class Page {
let app: XCUIApplication
diff --git a/ios/MullvadVPNUITests/RelayTests.swift b/ios/MullvadVPNUITests/RelayTests.swift
index fe3d18c509..f1d3c6a501 100644
--- a/ios/MullvadVPNUITests/RelayTests.swift
+++ b/ios/MullvadVPNUITests/RelayTests.swift
@@ -17,14 +17,14 @@ private struct RelayInfo {
class RelayTests: LoggedInWithTimeUITestCase {
var removeFirewallRulesInTearDown = false
- override func setUp() {
- super.setUp()
+ override func setUp() async throws {
+ try await super.setUp()
removeFirewallRulesInTearDown = false
}
- override func tearDown() {
- super.tearDown()
+ override func tearDown() async throws {
+ try await super.tearDown()
if removeFirewallRulesInTearDown {
FirewallClient().removeRules()
diff --git a/ios/MullvadVPNUITests/SettingsMigrationTests.swift b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
index bf616fac2b..64e631deb9 100644
--- a/ios/MullvadVPNUITests/SettingsMigrationTests.swift
+++ b/ios/MullvadVPNUITests/SettingsMigrationTests.swift
@@ -43,8 +43,8 @@ class SettingsMigrationTests: BaseUITestCase {
return false
}
- override func setUp() {
- super.setUp()
+ override func setUp() async throws {
+ try await super.setUp()
agreeToTermsOfServiceIfShown()
diff --git a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
index 926b94a9e3..ab88b22167 100644
--- a/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
+++ b/ios/PacketTunnel/DeviceCheck/DeviceCheckOperation.swift
@@ -66,7 +66,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda
Begins the flow by fetching device state and then fetching account and device data. Calls `didReceiveData()` with
the received data when done.
*/
- private func startFlow(completion: @escaping (Result<DeviceCheck, Error>) -> Void) {
+ private func startFlow(completion: @escaping @Sendable (Result<DeviceCheck, Error>) -> Void) {
do {
guard case let .loggedIn(accountData, deviceData) = try deviceStateAccessor.read() else {
throw DeviceCheckError.invalidDeviceState
@@ -90,7 +90,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda
private func didReceiveData(
accountResult: Result<Account, Error>,
deviceResult: Result<Device, Error>,
- completion: @escaping (Result<DeviceCheck, Error>) -> Void
+ completion: @escaping @Sendable (Result<DeviceCheck, Error>) -> Void
) {
do {
let accountVerdict = try accountVerdict(from: accountResult)
@@ -158,7 +158,7 @@ final class DeviceCheckOperation: ResultOperation<DeviceCheck>, @unchecked Senda
then it rotate device key by marking the beginning of key rotation, updating device state and persisting before
proceeding to rotate the key.
*/
- private func rotateKeyIfNeeded(completion: @escaping (Result<KeyRotationStatus, Error>) -> Void) {
+ private func rotateKeyIfNeeded(completion: @escaping @Sendable (Result<KeyRotationStatus, Error>) -> Void) {
let deviceState: DeviceState
do {
deviceState = try deviceStateAccessor.read()
diff --git a/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift b/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift
index cb749f68a5..a86bebbd54 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/NEProviderStopReason+Debug.swift
@@ -46,6 +46,8 @@ extension NEProviderStopReason: CustomStringConvertible {
return "sleep"
case .appUpdate:
return "app update"
+ case .internalError:
+ return "internal error"
@unknown default:
return "unknown value (\(rawValue))"
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
index 7ae35c4685..5345287b73 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Combine
+@preconcurrency import Combine
import Foundation
extension PacketTunnelActor {
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
index 9cf3833bb8..c467ca270d 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import MullvadTypes
+@preconcurrency import MullvadTypes
import Network
import WireGuardKitTypes
diff --git a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift
index 1c06c634f9..0da7f7a3f0 100644
--- a/ios/PacketTunnelCore/Pinger/PingerProtocol.swift
+++ b/ios/PacketTunnelCore/Pinger/PingerProtocol.swift
@@ -29,7 +29,7 @@ public struct PingerSendResult {
}
/// A type capable of sending and receving ICMP traffic.
-public protocol PingerProtocol {
+public protocol PingerProtocol: Sendable {
var onReply: ((PingerReply) -> Void)? { get set }
func startPinging(destAddress: IPv4Address) throws
diff --git a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
index 10807e7362..20defd7a90 100644
--- a/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
+++ b/ios/PacketTunnelCore/Pinger/TunnelPinger.swift
@@ -12,7 +12,7 @@ import Network
import PacketTunnelCore
import WireGuardKit
-public final class TunnelPinger: PingerProtocol {
+public final class TunnelPinger: PingerProtocol, @unchecked Sendable {
private var sequenceNumber: UInt16 = 0
private let stateLock = NSLock()
private let pingReceiveQueue: DispatchQueue
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift
index 664fd69ed9..d9a0a33250 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelDeviceInfoProtocol.swift
@@ -9,7 +9,7 @@
import Foundation
/// A type that can provide statistics and basic information about tunnel device.
-public protocol TunnelDeviceInfoProtocol {
+public protocol TunnelDeviceInfoProtocol: Sendable {
/// Returns tunnel interface name (i.e utun0) if available.
var interfaceName: String? { get }
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
index c1160d25f2..9d20efcc01 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitor.swift
@@ -13,7 +13,7 @@ import Network
import NetworkExtension
/// Tunnel monitor.
-public final class TunnelMonitor: TunnelMonitorProtocol {
+public final class TunnelMonitor: TunnelMonitorProtocol, @unchecked Sendable {
private let tunnelDeviceInfo: TunnelDeviceInfoProtocol
private let nslock = NSLock()
@@ -22,7 +22,6 @@ public final class TunnelMonitor: TunnelMonitorProtocol {
private let timings: TunnelMonitorTimings
private var pinger: PingerProtocol
- private var isObservingDefaultPath = false
private var timer: DispatchSourceTimer?
private var state: TunnelMonitorState
diff --git a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift
index 7e925ec932..ee1836bb22 100644
--- a/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift
+++ b/ios/PacketTunnelCore/TunnelMonitor/TunnelMonitorTimings.swift
@@ -8,7 +8,7 @@
import MullvadTypes
-public struct TunnelMonitorTimings {
+public struct TunnelMonitorTimings: Sendable {
/// Interval for periodic heartbeat ping issued when traffic is flowing.
/// Should help to detect connectivity issues on networks that drop traffic in one of directions,
/// regardless if tx/rx counters are being updated.
diff --git a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift
index e40f9d78e5..82e33e8c03 100644
--- a/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift
+++ b/ios/PacketTunnelCore/URLRequestProxy/ProxyURLRequest.swift
@@ -9,7 +9,7 @@
import Foundation
/// Struct describing serializable URLRequest data.
-public struct ProxyURLRequest: Codable {
+public struct ProxyURLRequest: Codable, Sendable {
public let id: UUID
public let url: URL
public let method: String?
diff --git a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
index 85737b4bc9..d340974131 100644
--- a/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
+++ b/ios/PacketTunnelCoreTests/EphemeralPeerExchangingPipelineTests.swift
@@ -89,10 +89,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase {
let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: true, enableDaita: false)
await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
- wait(
- for: [reconfigurationExpectation, negotiationSuccessful],
- timeout: .UnitTest.invertedTimeout
- )
+ await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout)
}
func testSingleHopDaitaPeerExchange() async throws {
@@ -123,10 +120,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase {
let connectionState = stubConnectionState(enableMultiHop: false, enablePostQuantum: false, enableDaita: true)
await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
- wait(
- for: [reconfigurationExpectation, negotiationSuccessful],
- timeout: .UnitTest.invertedTimeout
- )
+ await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout)
}
func testMultiHopPostQuantumKeyExchange() async throws {
@@ -158,10 +152,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase {
let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: true, enableDaita: false)
await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
- wait(
- for: [reconfigurationExpectation, negotiationSuccessful],
- timeout: .UnitTest.invertedTimeout
- )
+ await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout)
}
func testMultiHopDaitaExchange() async throws {
@@ -188,10 +179,7 @@ final class EphemeralPeerExchangingPipelineTests: XCTestCase {
let connectionState = stubConnectionState(enableMultiHop: true, enablePostQuantum: false, enableDaita: true)
await postQuantumKeyExchangingPipeline.startNegotiation(connectionState, privateKey: PrivateKey())
- wait(
- for: [reconfigurationExpectation, negotiationSuccessful],
- timeout: .UnitTest.invertedTimeout
- )
+ await fulfillment(of: [reconfigurationExpectation, negotiationSuccessful], timeout: .UnitTest.invertedTimeout)
}
func stubConnectionState(
diff --git a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift
index 9766aac9ec..341e41eba6 100644
--- a/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/EphemeralPeerExchangeActorStub.swift
@@ -12,7 +12,7 @@ import NetworkExtension
@testable import PacketTunnelCore
@testable import WireGuardKitTypes
-final class EphemeralPeerExchangeActorStub: EphemeralPeerExchangeActorProtocol {
+final class EphemeralPeerExchangeActorStub: EphemeralPeerExchangeActorProtocol, @unchecked Sendable {
typealias KeyNegotiationResult = Result<(PreSharedKey, PrivateKey), EphemeralPeerExchangeErrorStub>
var result: KeyNegotiationResult = .failure(.unknown)
diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
index bb8d94a806..135b2a3e1c 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
@@ -8,8 +8,8 @@
import Foundation
import MullvadMockData
-import MullvadREST
-import PacketTunnelCore
+@preconcurrency import MullvadREST
+@preconcurrency import PacketTunnelCore
extension PacketTunnelActorTimings {
static var timingsForTests: PacketTunnelActorTimings {
diff --git a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
index 9c468fd45d..bab560267e 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PingerMock.swift
@@ -12,7 +12,7 @@ import Network
@testable import PacketTunnelCore
/// Ping client mock that can be used to simulate network transmission errors and delays.
-class PingerMock: PingerProtocol {
+class PingerMock: PingerProtocol, @unchecked Sendable {
typealias OutcomeDecider = (IPv4Address, UInt16) -> Outcome
private let decideOutcome: OutcomeDecider
diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift
index 40aaafee73..e6cde648ba 100644
--- a/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/TunnelDeviceInfoStub.swift
@@ -10,7 +10,7 @@ import Foundation
import PacketTunnelCore
/// Tunnel device stub that returns fixed interface name and feeds network stats from the type implementing `NetworkStatsProviding`
-struct TunnelDeviceInfoStub: TunnelDeviceInfoProtocol {
+struct TunnelDeviceInfoStub: TunnelDeviceInfoProtocol, @unchecked Sendable {
let networkStatsProviding: NetworkStatsProviding
var interfaceName: String? {
diff --git a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
index 4e77a6b617..7221e72a55 100644
--- a/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
+++ b/ios/PacketTunnelCoreTests/MultiHopEphemeralPeerExchangerTests.swift
@@ -90,8 +90,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase {
await multiHopExchanger.start()
- wait(
- for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
@@ -133,8 +133,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase {
})
await multiHopPeerExchanger.start()
- wait(
- for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
@@ -171,8 +171,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase {
})
await multiHopPeerExchanger.start()
- wait(
- for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
@@ -213,8 +213,8 @@ final class MultiHopEphemeralPeerExchangerTests: XCTestCase {
})
await multiHopPeerExchanger.start()
- wait(
- for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
index 484e69ffe1..2c4e145b1c 100644
--- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
+++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import Combine
+@preconcurrency import Combine
@testable import MullvadMockData
@testable import MullvadREST
@testable import MullvadSettings
diff --git a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
index cba495a596..7707ffabf7 100644
--- a/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
+++ b/ios/PacketTunnelCoreTests/SingleHopEphemeralPeerExchangerTests.swift
@@ -68,8 +68,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase {
await singleHopPostQuantumKeyExchanging.start()
- wait(
- for: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [expectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
@@ -110,8 +110,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase {
})
await singleHopPostQuantumKeyExchanging.start()
- wait(
- for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
@@ -147,8 +147,8 @@ final class SingleHopEphemeralPeerExchangerTests: XCTestCase {
})
await multiHopPeerExchanger.start()
- wait(
- for: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
+ await fulfillment(
+ of: [unexpectedNegotiationFailure, reconfigurationExpectation, negotiationSuccessful],
timeout: .UnitTest.invertedTimeout
)
}
diff --git a/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift b/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift
index 5f15b7cf45..62d0fc057f 100644
--- a/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift
+++ b/ios/PacketTunnelCoreTests/TunnelMonitorTests.swift
@@ -15,7 +15,7 @@ import XCTest
final class TunnelMonitorTests: XCTestCase {
let networkCounters = NetworkCounters()
- func testShouldDetermineConnectionEstablished() throws {
+ func testShouldDetermineConnectionEstablished() async throws {
let connectedExpectation = expectation(description: "Should report connected.")
let connectionLostExpectation = expectation(description: "Should not report connection loss")
connectionLostExpectation.isInverted = true
@@ -38,10 +38,10 @@ final class TunnelMonitorTests: XCTestCase {
tunnelMonitor.start(probeAddress: .loopback)
- waitForExpectations(timeout: .UnitTest.invertedTimeout)
+ await fulfillment(of: [connectedExpectation, connectionLostExpectation], timeout: .UnitTest.invertedTimeout)
}
- func testInitialConnectionTimings() {
+ func testInitialConnectionTimings() async throws {
// Setup pinger so that it never receives any replies.
let pinger = PingerMock(networkStatsReporting: networkCounters) { _, _ in .ignore }
@@ -108,7 +108,7 @@ final class TunnelMonitorTests: XCTestCase {
// Start monitoring.
tunnelMonitor.start(probeAddress: .loopback)
- waitForExpectations(timeout: TimeInterval(timeout) / 1000)
+ await fulfillment(of: [expectation], timeout: TimeInterval(timeout) / 1000)
}
}